`timescale 1ns/100ps
`default_nettype none

/***********************************************
 * Write Calculated IDCT data (S) 
 * from dual port RAM
 * to SRAM
 ***********************************************/
module SWrite (
    input logic CLOCK_50_I,             // 50 MHz clock
    input logic resetn,                       // Master reset
    input logic clearResult,            // clear result from previous run
    input logic yDone_in,
    
    input logic [31:0] DP_Read,         // Dual port RAM read data
    input logic [15:0] SRAM_Read,       // SRAM read data
    input logic [17:0] SRAM_BaseAddr,   // SRAM base address for start writing

    input logic start,                  // Module start signal

    output logic [6:0] DP_Addr,         // Dual port RAM address line
    output logic [31:0] DP_Write,       // Dual port RAM write data
    output logic DP_we,                 // Dual port RAM write enable

    output logic [17:0] SRAM_Addr,      // SRAM address line
    output logic [15:0] SRAM_Write,     // SRAM write data
    output logic SRAM_we,               // SRAM write enable

    output logic finish                 // Module finish indication
);

logic yDone;          // Indicate if yuv Y write is done when we start
logic [17:0] baseOffset;    // baseOffset for each row
logic [2:0] row;      // Row offset, from 0 to 7
logic [2:0] col;      // Column offset, from 0 to 7
logic [7:0] SRAM_WriteBuffer;   // Buffer first value for SRAM write
logic [7:0] DP_ReadClipped;     // Clipped value of DP_Read

enum logic [3:0] {
    S0,
    S1,
    S2,
    S3,
    S4,
    S5,
    S6,
    S7
} state;

// Provide clipping for values written back to SRAM
assign DP_ReadClipped = DP_Read[31] ? 8'b0 : (|DP_Read[30:8]) ? 8'd255 : DP_Read[7:0];

always_ff @ (posedge CLOCK_50_I or negedge resetn) begin
    if (resetn == 1'b0) begin
        yDone <= 1'b0;
        DP_Addr <= 7'b0;
        DP_Write <= 32'b0;
        DP_we <= 1'b0;
        SRAM_Addr <= 18'b0;
        SRAM_Write <= 16'b0;
        SRAM_we <= 1'b0;

        baseOffset <= SRAM_BaseAddr;
        row <= 3'b0;
        col <= 3'b0;

        finish <= 1'b0;
        
        state <= S0;
    end else begin
        case (state)
        S0: begin
            if (start) begin
                // Prepare First Address of S
                yDone <= yDone_in;
                DP_Addr <= 7'd64;
                DP_Write <= 32'b0;
                DP_we <= 1'b0;
                SRAM_Addr <= 18'b0;
                SRAM_Write <= 16'b0;
                SRAM_we <= 1'b0;

                baseOffset <= SRAM_BaseAddr;
                row <= 3'b0;
                col <= 3'b0;

                finish <= 1'b0;
                
                state <= S1;
            end else if (clearResult) begin
                // Prepare First Address of S
                DP_Addr <= 7'd64;
                DP_Write <= 32'b0;
                DP_we <= 1'b0;
                SRAM_Addr <= 18'b0;
                SRAM_Write <= 16'b0;
                SRAM_we <= 1'b0;

                baseOffset <= SRAM_BaseAddr;
                row <= 3'b0;
                col <= 3'b0;

                finish <= 1'b0;
             end
        end
        S1: begin
            // Advance DP RAM Address
            DP_Addr <= DP_Addr + 7'b1;

            state <= S2;
        end
        S2: begin
            // Advance DP RAM Address
            DP_Addr <= DP_Addr + 7'b1;

            // Dual Port RAM data stable for the first time,
            //  store 8 LSB in buffer
            SRAM_WriteBuffer <= DP_ReadClipped;

            // Advance column counter
            col <= col + 3'b1;

            state <= S3;
        end
        S3: begin
            // Advance DP RAM Address
            DP_Addr <= DP_Addr + 7'b1;

            // Set up SRAM for write at next clock cycle
            // SRAM_Addr=baseOffset+col/2
            SRAM_Addr <= baseOffset + col[2:1];
            SRAM_Write <= {SRAM_WriteBuffer, DP_ReadClipped};
            SRAM_we <= 1'b1;

            // Logic to drive column, row counter, and baseOffset
            // This will only happen in S3 not S4
            if (col == 3'd7) begin
                // Reset column count
                col <= 3'b0;
                // Advance row count
                row <= row + 3'b1;
                if (yDone==1'b0) begin
                    // Change baseOffset to next row
                    baseOffset <= baseOffset + 18'd160;
                end else begin
                    // Change baseOffset to next row of U and V
                    baseOffset <= baseOffset + 18'd80;
                end
            end else begin
                col <= col + 3'b1;
            end
            
            state <= S4;
        end
        S4: begin
            // Advance DP RAM Address
            DP_Addr <= DP_Addr + 7'b1;

            // Unset SRAM_we
            SRAM_we <= 1'b0;

            // Store Dual Port RAM data in buffer
            SRAM_WriteBuffer <= DP_ReadClipped;
            
            // See if we are finished
            if (col == 3'd6 && row == 3'd7) begin
                state <= S5;
            end else begin
                col <= col + 3'b1;
                state <= S3;
            end
        end
        S5: begin
            // Set up SRAM for write at next clock cycle
            // SRAM_Addr=baseOffset+col/2
            SRAM_Addr <= baseOffset + col[2:1];
            SRAM_Write <= {SRAM_WriteBuffer, DP_ReadClipped};
            SRAM_we <= 1'b1;

            state <= S6;
        end
        S6: begin
            // Unset SRAM_we
            SRAM_we <= 1'b0;
            
            state <= S7;
        end
        S7: begin
            // SRAM will finish writing at next cycle,
            //  go back to IDLE, set finish,
            //  and ask for the cake (or be sent to fire chamber)
            finish <= 1'b1;
            state <= S0;
        end
        default: state <= S0;
        endcase
    end
end

endmodule
